///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_MAT_OPERATORS_H_
#define _BUOLA_MAT_OPERATORS_H_

#include <buola/mat/cexpr.h>
#include <buola/mat/detail/celementbinaryop.h>
#include <buola/mat/detail/celementunaryop.h>
#include <buola/mat/detail/cproduct.h>
#include <buola/functors/predicates/unary.h>
#include <buola/functors/predicates/binary.h>
#include <buola/functors/transform/binary.h>
#include <buola/functors/transform/unary.h>

namespace buola { namespace mat {

///\todo see if it is better to make these functions inline

///\addtogroup matrix
///@{

///\name Element-wise arithmetic operators
//@{

///addition operator
///
///Returns an expression representing the sum of the two subexpressions \p pLH and \p pRH. The new expression can 
///then be used with other operators, manipulated through the interface provided by \c CExpr or assigned to a
///matrix or assignable expression
template<typename tLH,typename tRH>
detail::OElementBinaryOpResult<const tLH&,const tRH&,fn::AAdd>
operator+(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Derived(),{}};
}
    
///subtraction operator
///
///Returns an expression representing the subtraction of the two subexpressions \p pLH and \p pRH. The new expression 
///can then be used with other operators, manipulated through the interface provided by \c CExpr or assigned to a
///matrix or assignable expression
template<typename tLH,typename tRH>
detail::OElementBinaryOpResult<const tLH&,const tRH&,fn::ASubtract>
operator-(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Derived(),{}};
}

///element-wise division operator
///
///Returns an expression representing the element-wise division of the two subexpressions \p pLH and \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH,typename tRH>
detail::OElementBinaryOpResult<const tLH&,const tRH&,fn::ADivide>
operator/(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Derived(),{}};
}

///element-wise division operator
///
///Returns an expression representing the element-wise product of the two subexpressions \p pLH and \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH,typename tRH>
detail::OElementBinaryOpResult<const tLH&,tRH,fn::AMultiply>
operator*(const CExpr<tLH> &pLH,const detail::CStarOp<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Sub(),{}};
}

///product of an expression by a scalar
///
///Returns an expression representing the result of multiplying each element of \p pLH by the scalar \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::AMultiplyConstant<detail::OScalar<tLH>>>
operator*(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::AMultiplyConstant<detail::OScalar<tLH>>(pRH)};
}

///product of an expression by a scalar
///
///Returns an expression representing the result of multiplying the scalar \p pLH by each element of \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::AMultiplyConstant<detail::OScalar<tRH>>>
operator*(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::AMultiplyConstant<detail::OScalar<tRH>>(pLH)};
}

///addition of a scalar to an expression
///
///Returns an expression representing the result of adding the scalar \p pRH to each element of \p pLH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::AAddConstant<detail::OScalar<tLH>>>
operator+(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::AAddConstant<detail::OScalar<tLH>>(pRH)};
}

///addition of a scalar to an expression
///
///Returns an expression representing the result of adding the scalar \p pLH to each element of \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::AAddConstant<detail::OScalar<tRH>>>
operator+(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::AAddConstant<detail::OScalar<tRH>>(pLH)};
}

///subtraction of a scalar from an expression
///
///Returns an expression representing the result of subtracting the scalar \p pRH from each element of \p pLH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::ASubtractConstant<detail::OScalar<tLH>>>
operator-(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::ASubtractConstant<detail::OScalar<tLH>>(pRH)};
}

///subtraction of an expression from a scalar
///
///Returns an expression representing the result of subtracting each element of \p pRH from the scalar \p pLH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::ASubtractFromConstant<detail::OScalar<tRH>>>
operator-(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::ASubtractFromConstant<detail::OScalar<tRH>>(pLH)};
}

///division of an expression by a scalar
///
///Returns an expression representing the result of dividing each element of \p pLH by the scalar \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::ADivideConstant<detail::OScalar<tLH>>>
operator/(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::ADivideConstant<detail::OScalar<tLH>>(pRH)};
}

///element-wise division of a scalar by an expression
///
///Returns an expression representing the result of dividing the scalar \p pLH by each element of \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::ADivideFromConstant<detail::OScalar<tRH>>>
operator/(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::ADivideFromConstant<detail::OScalar<tRH>>(pLH)};
}

///element-wise negation of an expression
///
///Returns an expression representing the result of negating each element of \p pRH. The new 
///expression can then be used with other operators, manipulated through the interface provided by \c CExpr or 
///assigned to a matrix or assignable expression
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::AMinus>
operator-(const CExpr<tLH> &pLH)
{
    return {pLH.Derived(),{}};
}

//@}

///\name comparison operators
//@{
    
///element-wise less than of an expression with respect to a scalar
///
///Returns an expression representing a boolean matrix where each element is set to true if the corresponding element
///in \p pLH is less than the scalar \p pRH.
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::ALessThan<detail::OScalar<tLH>>>
operator<(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::less_than(pRH)};
}

///element-wise less than of a scalar with respect to an expression
///
///Returns an expression representing a boolean matrix where each element is set to true if the scalar \p pLH is less
///than the corresponding element in \p pRH.
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::AGreaterThan<detail::OScalar<tRH>>>
operator<(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::greater_than(pLH)};
}

///element-wise greater than of an expression with respect to a scalar
///
///Returns an expression representing a boolean matrix where each element is set to true if the corresponding element
///in \p pLH is greater than the scalar \p pRH.
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::AGreaterThan<detail::OScalar<tLH>>>
operator>(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::greater_than(pRH)};
}

///element-wise greater than of a scalar with respect to an expression
///
///Returns an expression representing a boolean matrix where each element is set to true if the scalar \p pLH is greater
///than the corresponding element in \p pRH.
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::ALessThan<detail::OScalar<tRH>>>
operator>(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::less_than(pLH)};
}

///element-wise equal of an expression with respect to a scalar
///
///Returns an expression representing a boolean matrix where each element is set to true if the corresponding element
///in \p pLH is equal to the scalar \p pRH.
template<typename tLH>
detail::OElementUnaryOpResult<const tLH&,fn::AEqualTo<detail::OScalar<tLH>>>
operator==(const CExpr<tLH> &pLH,detail::OScalar<tLH> pRH)
{
    return {pLH.Derived(),fn::equal_to(pRH)};
}

///element-wise equal of a scalar with respect to an expression
///
///Returns an expression representing a boolean matrix where each element is set to true if the scalar \p pLH is equal
///to the corresponding element in \p pRH.
template<typename tRH>
detail::OElementUnaryOpResult<const tRH&,fn::AEqualTo<detail::OScalar<tRH>>>
operator==(detail::OScalar<tRH> pLH,const CExpr<tRH> &pRH)
{
    return {pRH.Derived(),fn::equal_to(pLH)};
}

///matrix comparison
///
///Returns a boolean which is true if every element in the expression \p pLH is equal to the corresponding element in
///the expression \p pRH
template<typename tLH,typename tRH>
detail::OElementBinaryOpResult<const tLH&,const tRH&,fn::AEqual> operator==(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Derived(),{}};
}

///matrix comparison
///
///Returns a boolean which is true if every element in the expression \p pLH is equal to the corresponding element in
///the expression \p pRH
template<typename tLH,typename tRH>
detail::OElementBinaryOpResult<const tLH&,const tRH&,fn::ANotEqual> operator!=(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Derived(),{}};
}

//@}

///\name matrix multiplication
//@{

///general matrix multiplication
///
///Returns an expression representing the result of performing matrix multiplication of \p pLH by \p pRH, where
///the number of columns in the expression \p pLH must be equal to the number of rows in the expression \p pRH.
template<typename tLH,typename tRH>
OEnableIfNot<detail::QOneByOneProduct<tLH,tRH>,detail::CProduct<const tLH&,const tRH&>>
operator*(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.Derived(),pRH.Derived()};
}

///multiplication of row vector by column vector
///
///Returns the scalar which results from multiplying the row vector expression \p pLH by the column vector expression
///\p pRH
template<typename tLH,typename tRH>
OEnableIf<detail::QOneByOneProduct<tLH,tRH>,detail::OScalar<tLH>>
operator*(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return detail::OScalar<tLH>(detail::CProduct<const tLH&,const tRH&>(pLH.Derived(),pRH.Derived()));
}

///dot product
///
///Returns the scalar which results from multiplying the vector expression \p pLH as a row by the vector expression
///\p pRH as a column
template<typename tLH,typename tRH>
detail::OScalar<tLH> dot(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    detail::OScalar<tLH> lResult=0;
    const tLH &lLH=pLH.Derived();
    const tRH &lRH=pRH.Derived();
    for(int i=0;i<lLH.size();i++)
        lResult+=lLH.template At<detail::OScalar<tLH>>(i)*lRH.template At<detail::OScalar<tLH>>(i);
    return lResult;
}

///cross product
template<typename tLH,typename tRH>
mat::CVec<detail::OScalar<tLH>,3> cross(const CExpr<tLH> &pLH,const CExpr<tRH> &pRH)
{
    return {pLH.y()*pRH.z()-pLH.z()*pRH.y(),
            pLH.z()*pRH.x()-pLH.x()*pRH.z(),
            pLH.x()*pRH.y()-pLH.y()*pRH.x()};
}

//@}

///\name assignment operators
//@{

///element-wise division assignment
///
///Divides each element in \p pLH by the corresponding element of \p pRH or by \p pRH if it is a scalar.
///
///It is equivalent to
///
///~~~~~~~~~~~~~~~~
///pLH=pLH/pRH
///~~~~~~~~~~~~~~~~

template<typename tLH,typename tRH>
tLH& operator/=(CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH/std::forward<tRH>(pRH);
}

///matrix multiplication assignment
///
///Divides each element in \p pLH by the corresponding element of \p pRH or by \p pRH if it is a scalar.
///
///It is equivalent to
///
///~~~~~~~~~~~~~~~~
///pLH=pLH/pRH
///~~~~~~~~~~~~~~~~

template<typename tLH,typename tRH>
tLH& operator*=(CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH*std::forward<tRH>(pRH);
}

template<typename tLH,typename tRH>
const tLH& operator*=(const CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH*std::forward<tRH>(pRH);
}

template<typename tLH,typename tRH>
tLH& operator+=(CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH+std::forward<tRH>(pRH);
}

template<typename tLH,typename tRH>
const tLH& operator+=(const CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH+std::forward<tRH>(pRH);
}

template<typename tLH,typename tRH>
tLH& operator-=(CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH-std::forward<tRH>(pRH);
}

template<typename tLH,typename tRH>
const tLH& operator-=(const CExpr<tLH> &pLH,tRH &&pRH)
{
    return pLH.Derived()=pLH-std::forward<tRH>(pRH);
}

/*namespace mat*/ } /*namespace buola*/ }

#endif
